package net.sourceforge.peers.sip.core.useragent;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Hashtable;

import net.sourceforge.peers.Logger;
import net.sourceforge.peers.sip.RFC3261;
import net.sourceforge.peers.sip.core.useragent.handlers.ByeHandler;
import net.sourceforge.peers.sip.core.useragent.handlers.CancelHandler;
import net.sourceforge.peers.sip.core.useragent.handlers.InviteHandler;
import net.sourceforge.peers.sip.core.useragent.handlers.OptionsHandler;
import net.sourceforge.peers.sip.core.useragent.handlers.RegisterHandler;
import net.sourceforge.peers.sip.syntaxencoding.SipHeaderFieldName;
import net.sourceforge.peers.sip.syntaxencoding.SipHeaderFieldValue;
import net.sourceforge.peers.sip.syntaxencoding.SipHeaders;
import net.sourceforge.peers.sip.syntaxencoding.SipURI;
import net.sourceforge.peers.sip.transaction.ClientTransaction;
import net.sourceforge.peers.sip.transaction.ClientTransactionUser;
import net.sourceforge.peers.sip.transaction.ServerTransaction;
import net.sourceforge.peers.sip.transaction.ServerTransactionUser;
import net.sourceforge.peers.sip.transaction.Transaction;
import net.sourceforge.peers.sip.transaction.TransactionManager;
import net.sourceforge.peers.sip.transactionuser.Dialog;
import net.sourceforge.peers.sip.transactionuser.DialogManager;
import net.sourceforge.peers.sip.transport.SipRequest;
import net.sourceforge.peers.sip.transport.SipResponse;
import net.sourceforge.peers.sip.transport.TransportManager;


public class MidDialogRequestManager extends RequestManager implements ClientTransactionUser, ServerTransactionUser {

    public MidDialogRequestManager(UserAgent userAgent, InviteHandler inviteHandler, CancelHandler cancelHandler, ByeHandler byeHandler, OptionsHandler optionsHandler, RegisterHandler registerHandler, DialogManager dialogManager, TransactionManager transactionManager, TransportManager transportManager, Logger logger) {
        super(userAgent, inviteHandler, cancelHandler, byeHandler, optionsHandler, registerHandler, dialogManager, transactionManager, transportManager, logger);
    }


    ////////////////////////////////////////////////
    // methods for UAC
    ////////////////////////////////////////////////

    public void generateMidDialogRequest(Dialog dialog, String method, MessageInterceptor messageInterceptor) {


        SipRequest sipRequest = dialog.buildSubsequentRequest(RFC3261.METHOD_BYE);

        if (RFC3261.METHOD_BYE.equals(method)) {
            byeHandler.preprocessBye(sipRequest, dialog);
        }
        //TODO check that subsequent request is supported before client
        //transaction creation
        if (!RFC3261.METHOD_INVITE.equals(method)) {
            ClientTransaction clientTransaction = createNonInviteClientTransaction(sipRequest, null, byeHandler);
            if (messageInterceptor != null) {
                messageInterceptor.postProcess(sipRequest);
            }
            if (clientTransaction != null) {
                clientTransaction.start();
            }
        }


    }


    public ClientTransaction createNonInviteClientTransaction(SipRequest sipRequest, String branchId, ClientTransactionUser clientTransactionUser) {
        //8.1.2
        SipURI destinationUri = RequestManager.getDestinationUri(sipRequest, logger);

        //TODO if header route is present, addrspec = toproute.nameaddress.addrspec
        String transport = RFC3261.TRANSPORT_UDP;
        Hashtable<String, String> params = destinationUri.getUriParameters();
        if (params != null) {
            String reqUriTransport = params.get(RFC3261.PARAM_TRANSPORT);
            if (reqUriTransport != null) {
                transport = reqUriTransport;
            }
        }
        int port = destinationUri.getPort();
        if (port == SipURI.DEFAULT_PORT) {
            port = RFC3261.TRANSPORT_DEFAULT_PORT;
        }
        SipURI sipUri = userAgent.getConfig().getOutboundProxy();
        if (sipUri == null) {
            sipUri = destinationUri;
        }
        InetAddress inetAddress;
        try {
            inetAddress = InetAddress.getByName(sipUri.getHost());
        } catch (UnknownHostException e) {
            logger.error("unknown host: " + sipUri.getHost(), e);
            return null;
        }
        return transactionManager.createClientTransaction(sipRequest, inetAddress, port, transport, branchId, clientTransactionUser);
    }


    ////////////////////////////////////////////////
    // methods for UAS
    ////////////////////////////////////////////////

    public void manageMidDialogRequest(SipRequest sipRequest, Dialog dialog) {
        SipHeaders sipHeaders = sipRequest.getSipHeaders();
        SipHeaderFieldValue cseq = sipHeaders.get(new SipHeaderFieldName(RFC3261.HDR_CSEQ));
        String cseqStr = cseq.getValue();
        int pos = cseqStr.indexOf(' ');
        if (pos < 0) {
            pos = cseqStr.indexOf('\t');
        }
        int newCseq = Integer.parseInt(cseqStr.substring(0, pos));
        int oldCseq = dialog.getRemoteCSeq();
        if (oldCseq == Dialog.EMPTY_CSEQ) {
            dialog.setRemoteCSeq(newCseq);
        } else if (newCseq < oldCseq) {
            // out of order
            // RFC3261 12.2.2 p77
            // TODO test out of order in-dialog-requests
            SipResponse sipResponse = generateResponse(sipRequest, dialog, RFC3261.CODE_500_SERVER_INTERNAL_ERROR, RFC3261.REASON_500_SERVER_INTERNAL_ERROR);
            ServerTransaction serverTransaction = transactionManager.createServerTransaction(sipResponse, userAgent.getSipPort(), RFC3261.TRANSPORT_UDP, this, sipRequest);
            serverTransaction.start();
            serverTransaction.receivedRequest(sipRequest);
            serverTransaction.sendReponse(sipResponse);
        } else {
            dialog.setRemoteCSeq(newCseq);
        }

        String method = sipRequest.getMethod();
        if (RFC3261.METHOD_BYE.equals(method)) {
            byeHandler.handleBye(sipRequest, dialog);
        } else if (RFC3261.METHOD_INVITE.equals(method)) {
            inviteHandler.handleReInvite(sipRequest, dialog);
        } else if (RFC3261.METHOD_ACK.equals(method)) {
            inviteHandler.handleAck(sipRequest, dialog);
        } else if (RFC3261.METHOD_OPTIONS.equals(method)) {
            optionsHandler.handleOptions(sipRequest);
        }
    }

    // ServerTransactionUser methods
    @Override
    public void transactionFailure() {

    }


    // ClientTransactionUser methods
    // callbacks employed for cancel responses (ignored)
    @Override
    public void transactionTimeout(ClientTransaction clientTransaction) {

    }


    @Override
    public void provResponseReceived(SipResponse sipResponse, Transaction transaction) {

    }


    @Override
    public void errResponseReceived(SipResponse sipResponse) {

    }


    @Override
    public void successResponseReceived(SipResponse sipResponse, Transaction transaction) {

    }


    @Override
    public void transactionTransportError() {

    }
}
